#if (PocketPC || Smartphone || WindowsCE)
#else
//#define CODE_COMPILE
#endif

#if (mono)
#define CODE_COMPILE
#endif

using System;
using System.Collections.Generic;
using System.Text;
#if CODE_COMPILE
using System.CodeDom.Compiler;
using Microsoft.CSharp;
#endif

namespace RaisingStudio.Data.Common.Factories
{
    public class ObjectAccessorFactory
    {
#if CODE_COMPILE
        public static readonly string ACCESSORTYPENAME = "_";
#else
        DynamicObjectAccessorFactory dynamicObjectAccessorFactory = new DynamicObjectAccessorFactory();
#endif
        
        public IObjectAccessor CreateObjectAccessor(System.Type type, string[] propertyNames, out Dictionary<string, System.Type> propertyTypes)
        {
#if CODE_COMPILE
            string accessorTypeName = ACCESSORTYPENAME;
            string code = GenerateObjectAccessorCSharpCode(type, propertyNames, out propertyTypes, accessorTypeName);
            System.Type accessorType = GenerateTypeFromCSharpCode(type, code, accessorTypeName);
            if (accessorType != null)
            {
                return System.Activator.CreateInstance(accessorType) as IObjectAccessor;
            }
            return null;
#else
            return this.dynamicObjectAccessorFactory.CreateObjectAccessor(type, propertyNames, out propertyTypes);
#endif
        }

#if CODE_COMPILE
        public static readonly string ACCESSORTYPENAME_MARK = "$ACCESSORTYPENAME$";
        public static readonly string TYPENAME_MARK = "$TYPENAME$";

        public static readonly string GET_CASE_MARK = "$$GET_CASE$$";
        public static readonly string SET_CASE_MARK = "$$SET_CASE$$";

        public static readonly string PROPERTYNAME_MARK = "$PROPERTYNAME$";
        public static readonly string PROPERTYTYPE_MARK = "$PROPERTYTYPE$";

        #region Templates
        public static readonly string GETCASE_TEMPLATE_CODE = @"
case " + "\"$PROPERTYNAME$\":" + @"
               {
                   return _instance.$PROPERTYNAME$;
               }";

        public static readonly string SETCASE_TEMPLATE_CODE = @"
case " + "\"$PROPERTYNAME$\":" + @"
                {
                    _instance.$PROPERTYNAME$ = ($PROPERTYTYPE$)value;
                    return;
                }";

        public static readonly string ACCESSOR_TEMPLATE_CODE = @"
class $ACCESSORTYPENAME$ : RaisingStudio.Data.Common.IObjectAccessor, RaisingStudio.Data.Common.IObjectCreater
{
    public object CreateObject()
    {
        return new $TYPENAME$();
    }

    public System.Collections.IList CreateObjectList()
    {
        return new System.Collections.Generic.List<$TYPENAME$>();
    }

    public object GetValue(object instance, string name)
    {
        $TYPENAME$ _instance = instance as $TYPENAME$;
        switch (name)
        {
            $$GET_CASE$$
            default:
                {
                    throw new System.ArgumentOutOfRangeException(name);
                }
        }
    }
    public void SetValue(object instance, string name, object value)
    {
        $TYPENAME$ _instance = instance as $TYPENAME$;
        switch (name)
        {
            $$SET_CASE$$
            default:
                {
                    throw new System.ArgumentOutOfRangeException(name);
                }
        }
    }
}";
        #endregion
#endif

        public static System.Type GetPropertyType(System.Type type, string propertyName)
        {
            System.Reflection.PropertyInfo propertyInfo = type.GetProperty(propertyName);
            if (propertyInfo != null)
            {
                System.Type propertyType = propertyInfo.PropertyType;
                return propertyType;
            }
            else
            {
                System.Reflection.FieldInfo fileInfo = type.GetField(propertyName);
                if (fileInfo != null)
                {
                    System.Type fileType = fileInfo.FieldType;
                    return fileType;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("propertyName");
                }
            }
        }

#if CODE_COMPILE
        protected string GenerateObjectAccessorCSharpCode(Type type, string[] propertyNames, out Dictionary<string, Type> propertyTypes, string accessorTypeName)
        {
            propertyTypes = new Dictionary<string, Type>();

            string typeName = type.FullName;
            System.Text.StringBuilder getValueCodeStringBuilder = new StringBuilder();
            System.Text.StringBuilder setValueCodeStringBuilder = new StringBuilder();
            foreach (string propertyName in propertyNames)
            {
                System.Type propertyType = GetPropertyType(type, propertyName);
                propertyTypes.Add(propertyName, propertyType);

                string propertyTypeCode = propertyType.FullName; ;
                if (propertyType.IsGenericType)
                {
                    if (propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        Type argumentsType = propertyType.GetGenericArguments()[0];
                        propertyTypeCode = string.Format("System.Nullable<{0}>", argumentsType);
                    }
                }

                string getValueCode = GETCASE_TEMPLATE_CODE;
                getValueCode = getValueCode.Replace(PROPERTYNAME_MARK, propertyName);
                getValueCodeStringBuilder.Append(getValueCode);

                string setValueCode = SETCASE_TEMPLATE_CODE;
                setValueCode = setValueCode.Replace(PROPERTYNAME_MARK, propertyName);
                setValueCode = setValueCode.Replace(PROPERTYTYPE_MARK, propertyTypeCode);
                setValueCodeStringBuilder.Append(setValueCode);
            }
            string getValueFinallyCode = getValueCodeStringBuilder.ToString();
            string setValueFinallyCode = setValueCodeStringBuilder.ToString();

            string accessorCode = ACCESSOR_TEMPLATE_CODE;
            accessorCode = accessorCode.Replace(ACCESSORTYPENAME_MARK, accessorTypeName);
            accessorCode = accessorCode.Replace(TYPENAME_MARK, typeName);
            accessorCode = accessorCode.Replace(GET_CASE_MARK, getValueFinallyCode);
            accessorCode = accessorCode.Replace(SET_CASE_MARK, setValueFinallyCode);
            return accessorCode;
        }

        public static string GetAssemblyLocation(System.Reflection.Assembly[] assemblies, string fullName)
        {
            foreach (System.Reflection.Assembly assembly in assemblies)
            {
                if (assembly.FullName == fullName)
                {
                    return assembly.Location;
                }
            }
            return null;
        }

        private static void AddReferenceAssemblies(System.Type type, CompilerParameters compilerParameters)
        {
            System.Reflection.Assembly typeAssembly = type.Assembly;
            System.Reflection.AssemblyName[] assemblyNames = typeAssembly.GetReferencedAssemblies();
            System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (System.Reflection.AssemblyName assemblyName in assemblyNames)
            {
                string fullName = assemblyName.FullName;
                string assemblyLocation = GetAssemblyLocation(assemblies, fullName);
                if (assemblyLocation != null)
                {
                    compilerParameters.ReferencedAssemblies.Add(assemblyLocation);
                }
            }
            compilerParameters.ReferencedAssemblies.Add(typeAssembly.Location);
        }

        protected Type GenerateTypeFromCSharpCode(Type type, string code, string typeName)
        {
            CodeDomProvider codeDomProvider = new CSharpCodeProvider();
            CompilerParameters compilerParameters = new CompilerParameters();
            AddReferenceAssemblies(typeof(ObjectAccessorFactory), compilerParameters);
            AddReferenceAssemblies(type, compilerParameters);

            compilerParameters.GenerateInMemory = true;

            CompilerResults compilerResults = codeDomProvider.CompileAssemblyFromSource(compilerParameters, code);
            if (compilerResults.Errors.Count == 0)
            {
                System.Reflection.Assembly compiledAssembly = compilerResults.CompiledAssembly;
                return compiledAssembly.GetType(typeName);
            }
            return null;
        }
#endif
    }
}
